package com.request.common.utils;

import cn.hutool.crypto.SecureUtil;
import com.request.common.enums.SignTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;

/**
 * Description: 签名工具类
 * Create DateTime: 2020-03-27 15:14
 *
 * 

 */
@Slf4j
public class SignUtils {

    /**
     * 判断签名是否正确。使用MD5签名。
     *
     * @param data 结构体
     * @param sign 客户端签名
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValidWithMD5(final TreeMap<String, String> data, String sign) {
        return isSignatureValid(data, sign, SignTypeEnum.MD5);
    }

    /**
     * 判断签名是否正确。使用SHA256签名。
     *
     * @param data 结构体
     * @param sign 客户端签名
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValidWithSha256(final TreeMap<String, String> data, String sign) {
        return isSignatureValid(data, sign, SignTypeEnum.SHA256);
    }

    /**
     * 判断签名是否正确，必须包含sign字段，否则返回false。
     *
     * @param data     结构体
     * @param sign     客户端签名
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(final TreeMap<String, String> data, String sign, SignTypeEnum signType) {
        if (data.isEmpty()) {
            return false;
        }
        return generateSignature(data, signType).equals(sign);
    }

    /**
     * 生成签名.
     *
     * @param data     待签名数据
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final TreeMap<String, String> data, SignTypeEnum signType) {
        return executeSignature(data, signType);
    }

    public static String syGenerateSignature(final TreeMap<String, String> data, SignTypeEnum signType, String appkey) {
        StringBuffer sb = new StringBuffer();
        data.forEach((key, value) -> {
            if (StringUtils.isBlank(value)) {
                log.error("【签名】签名生成失败：获取{}值为null，全部数据为{}", key, data.toString());
                throw new NullPointerException(String.format("%s relation value is null", key));
            }
            if (value.trim().length() > 0) {
                sb.append(key.trim()+"="+value.trim()+"&");
            }
        });
        sb.append(appkey);
        log.info("【Sy】推单验签的字符串:{}",sb.toString());
        if (SignTypeEnum.MD5.equals(signType)) {
            return SecureUtil.md5(sb.toString());
        } else if (SignTypeEnum.SHA256.equals(signType)) {
            return SecureUtil.sha256(sb.toString());
        } else {
            log.error("【签名】生成签名失败：无效的加密方式！");
            throw new RuntimeException(String.format("Invalid sign_type: %s", signType));
        }
    }

    public static String executeSignature(SortedMap<String, String> data, SignTypeEnum signType) {
        StringBuffer sb = new StringBuffer();
        data.forEach((key, value) -> {
            if (StringUtils.isBlank(value)) {
                log.error("【签名】签名生成失败：获取{}值为null，全部数据为{}", key, data.toString());
                throw new NullPointerException(String.format("%s relation value is null", key));
            }
            if (value.trim().length() > 0) {
                sb.append(value.trim());
            }
        });

        if (SignTypeEnum.MD5.equals(signType)) {
            return SecureUtil.md5(sb.toString());
        } else if (SignTypeEnum.SHA256.equals(signType)) {
            return SecureUtil.sha256(sb.toString());
        } else {
            log.error("【签名】生成签名失败：无效的加密方式！");
            throw new RuntimeException(String.format("Invalid sign_type: %s", signType));
        }
    }

    /**
     * xml转map 不带属性
     *
     * @param xmlStr      xml字符串
     * @param needRootKey 是否需要在返回的map里加根节点键
     * @return
     * @throws DocumentException
     */
    public static Map<String, Object> xml2map(String xmlStr, boolean needRootKey) {
        Document doc;
        try {
            doc = DocumentHelper.parseText(xmlStr);
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
        Element root = doc.getRootElement();
        Map xml2map = xml2map(root);
        Map<String, Object> map = (Map<String, Object>) xml2map;

        if (root.elements().size() == 0 && root.attributes().size() == 0) {
            return map;
        }
        if (needRootKey) {
            // 在返回的map里加根节点键（如果需要）
            Map<String, Object> rootMap = new HashMap<String, Object>();
            rootMap.put(root.getName(), map);
            return rootMap;
        }
        return map;
    }

    /**
     * xml转map 不带属性
     *
     * @param e
     * @return Map
     */
    public static Map<String, Object> xml2map(Element e) {
        Map<String, Object> map = new LinkedHashMap<>();
        List<Element> list = e.elements();
        if (list.size() > 0) {
            for (int i = 0; i < list.size(); i++) {
                Element iter = list.get(i);
                List<Object> mapList = new ArrayList<>();
                if (iter.elements().size() > 0) {
                    Map<String, Object> m = xml2map(iter);
                    if (map.get(iter.getName()) != null) {
                        Object obj = map.get(iter.getName());
                        if (!(obj instanceof List)) {
                            mapList = new ArrayList<>();
                            mapList.add(obj);
                            mapList.add(m);
                        }
                        if (obj instanceof List) {
                            mapList = (List) obj;
                            mapList.add(m);
                        }
                        map.put(iter.getName(), mapList);
                    } else {
                        map.put(iter.getName(), m);
                    }
                } else {
                    if (map.get(iter.getName()) != null) {
                        Object obj = map.get(iter.getName());
                        if (!(obj instanceof List)) {
                            mapList = new ArrayList<>();
                            mapList.add(obj);
                            mapList.add(iter.getText());
                        }
                        if (obj instanceof List) {
                            mapList = (List) obj;
                            mapList.add(iter.getText());
                        }
                        map.put(iter.getName(), mapList);
                    } else {
                        map.put(iter.getName(), iter.getText());
                    }
                }
            }
        } else {
            map.put(e.getName(), e.getText());
        }
        return map;
    }

    public static String md5(String source) throws Exception {
        return byte2hex(MessageDigest.getInstance("MD5").digest(source.getBytes(StandardCharsets.UTF_8)));
    }

    private static String byte2hex(byte[] bytes) {
        StringBuilder sign = new StringBuilder();

        for (byte aByte : bytes) {
            String hex = Integer.toHexString(aByte & 255);
            if (hex.length() == 1) {
                sign.append("0");
            }
            sign.append(hex.toUpperCase());
        }
        return sign.toString();
    }

}
